<!DOCTYPE html>
<html>
<head>
<title>N-body system - AssemblyScript</title>
<link rel="icon" href="https://assemblyscript.org/favicon.ico" type="image/x-icon" />
<meta name="viewport" content="user-scalable=0" />
<style>
html, body { height: 100%; margin: 0; overflow: hidden; color: #111; background: #fff; font-family: sans-serif; }
body { border-top: 2px solid #070809; }
h1 { padding: 18px 20px 20px; font-size: 12pt; margin: 0; }
a { color: #111; text-decoration: none; }
a:hover { color: #efbd03; text-decoration: underline; }
canvas { position: absolute; top: 60px; left: 20px; width: calc(100% - 40px); height: calc(100% - 80px); background: #070809; }
</style>
</head>
<body>
<h1>
  <a href="https://en.wikipedia.org/wiki/N-body_problem">N-body system</a> in
  <a href="http://assemblyscript.org">AssemblyScript</a>
  ( <a href="https://github.com/AssemblyScript/assemblyscript/blob/master/examples/n-body/assembly/index.ts">source</a> )
</h1>
<canvas></canvas>
<script>"use strict";

// Set up the canvas with a 2D rendering context
var cnv = document.getElementsByTagName("canvas")[0];
var ctx = cnv.getContext("2d");
var bcr = cnv.getBoundingClientRect();

// Compute the size of the universe (here: 2px per cell)
var width = bcr.width;
var height = bcr.height;

cnv.width = width;
cnv.height = height;
ctx.imageSmoothingEnabled = false;

// Fetch and instantiate the module
fetch("build/optimized.wasm")
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.instantiate(buffer, {
  env: {
    memory: new WebAssembly.Memory({ initial: 1 }),
    abort: function() { throw Error("abort called"); }
  }
}))
.then(module => {
  var exports = module.instance.exports;
  var mem = new Float64Array(exports.memory.buffer);

  exports.init();

  // Update about 30 times a second
  (function update() {
    setTimeout(update, 1000 / 30);
    for (var i = 0; i < 3; ++i) exports.step();
  })();

  // Add some random stars because stars
  var stars = [];
  for (var i = 0; i < 250; ++i) {
    var star = {
      x: Math.random() * width,
      y: Math.random() * height,
      r: 0.2 + Math.random() * 0.8
    };
    stars.push(star);
  }

  // Keep rendering
  (function render() {
    requestAnimationFrame(render);
    var cx = width / 2;
    var cy = height / 2;
    ctx.clearRect(0, 0, width, height);
    for (let i = 0, k = stars.length; i < k; ++i) {
      let star = stars[i];
      ctx.fillStyle = "#fff";
      ctx.beginPath();
      ctx.globalAlpha = 0.5 + 0.5 * Math.random();
      ctx.arc(star.x, star.y, star.r, 0, 2 * Math.PI);
      ctx.fill();
    }
    ctx.globalAlpha = 1.0;
    for (let i = 0;; ++i) {
      let body = exports.getBody(i);
      if (!body) break;
      let ptr = body >>> 3;
      let x = mem[ptr];
      let y = mem[ptr + 1];
      let z = mem[ptr + 2];
      let m = mem[ptr + 6];
      ctx.fillStyle = planets[i].color;
      ctx.beginPath();
      ctx.arc(cx + x * 10, cy + y * 10, 2 * planets[i].r, 0, 2 * Math.PI);
      ctx.fill();
    }
  })();

}).catch(err => {
  alert("Failed to load WASM: " + err.message + " (ad blocker, maybe?)");
  console.log(err.stack);
});

var planets = [
  { color: "#f7d864", r: 8.00 },
  { color: "#e2b37d", r: Math.sqrt(11.21) },
  { color: "#cdb086", r: Math.sqrt(9.45) },
  { color: "#588bce", r: Math.sqrt(4.01) },
  { color: "#0f6ab0", r: Math.sqrt(3.88) }
];
</script>
</body>
</html>
